#include <fcntl.h>
#include <string>
#include <termios.h>
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <sys/types.h>
#include <vector>

#define log(x) std::cout << x << std::endl;

int buads[] = {B9600, B115200, B19200};

struct Parameterset
{
	std::string name;
	std::string desc;
};

struct Parameters
{
	int baud = B115200;
	std::string port_path = "/dev/ttyUSB0";
	int type = 0;
};

struct data
{
	char head = '@';
	char to_send[50];
	char end = '\n';
};

void print_help (std::vector <Parameterset> parms);
void cli (int argc, char* args[], Parameters &params);
int SerialInit (const char* path);
int SerialSetting (int fd, int baudrate, int datasize);
int senddata (int fd, std::string to_send);
int receivedata (int fd);
void closeserial (int fd);

int main (int argc, char* args[])
{
	std::vector <Parameterset> parms = 
	{
		{"Usage : ", "serial_operate --port <port_file> --baud <baudrate> --type <receive|send>"},
		{"Default : ", "--port /dev/ttyUSB0 --baud 115200 --type send"},
		{"", ""},
		{"--help", "show help"},
		{"--port", "set the port_file"},
		{"--baud", "set the baudrate"},
		{"", "the available buadrate - 9600 19200 115200"},
		{"--type", "set the type - receive | send"}
	};

	Parameters params;

	cli (argc, args, params);

	if (params.type == 2)
	{
		print_help (parms);
		std::exit (0);
	}

	int fd = SerialInit (params.port_path.c_str ());
	int success = SerialSetting (fd, params.baud, CS8);
	if (!success)
	{
		std::exit (1);
	}

	if (params.type == 0)
	{
		log ("# --------- Send data mode --------- #");
		while (true)
		{
			std::string input_data;
			std::cin >> input_data;
			if (input_data == "exit")
			{
				break;
			}
			else
			{
				senddata (fd, input_data);
			}
		}
	}
	else if (params.type == 1)
	{
		log ("# --------- Receive data mode ---------- #");
		receivedata (fd);
	}
}

void print_help (std::vector <Parameterset> parms)
{
	for (int i = 0; i < parms.size (); i++)
	{
		log (parms[i].name << "\t" << parms[i].desc);
	}
}

void cli (int argc, char* args[], Parameters &params)
{
	for (int i = 1; i < argc; i++)
	{
		std::string arg = args[i];
		if (arg == "--help")
		{
			params.type = 2;
			return ;
		}
		else if (arg == "--port")
		{
			params.port_path = args[i + 1];
			i++;
			continue;
		}
		else if (arg == "--baud")
		{
			std::string baud = args[i + 1];
			if (baud == "9600")
			{
				params.baud = B9600;
			}
			else if (baud == "115200")
			{
				params.baud = B115200;
			}
			else if (baud == "19200")
			{
				params.baud = B19200;
			}
			else
			{
				log ("Baudrate error !");
			}
			i++;
			continue;
		}
		else if (arg == "--type")
		{
			std::string type = args[i + 1];
			if (type == "send")
			{
				params.type = 0;
			}
			else if (type == "receive")
			{
				params.type = 1;
			}
			else
			{
				log ("Type" << type << "not found !");
				params.type = 2;
			}
			i++;
			continue;
		}
		else
		{
			log ("No parameters named " << arg << "!");
			params.type = 2;
			i++;
			continue;
		}
	}
}

int SerialInit (const char* path)
{
	int fd;
	log ("----------------- Start Serial Port Control -----------------");
	fd = open (path, O_RDWR | O_NOCTTY | O_NDELAY);
	if (fd == -1)
	{
		log ("Open Port Failed !");
	}
	else
	{
		log ("Open Port" << path << "Successfully !");
	}

	return fd;
}

int SerialSetting (int fd, int buadrate, int datasize)
{
	termios SPSettings;
	// get the original settings
	tcgetattr (fd, &SPSettings);

	// Setting the buad rate
	cfsetispeed (&SPSettings, buadrate);
	cfsetospeed (&SPSettings, buadrate);

	// Setting the parity to be closed
	SPSettings.c_cflag &= ~PARENB;

	// Setting the stop bit to be 1 bit
	SPSettings.c_cflag &= ~CSTOPB;

	// Setting the data bit to be 8 bits
	SPSettings.c_cflag &= ~CSIZE; // clear the size
	SPSettings.c_cflag |= datasize; // set to 8 bits

	// Starting the char receive
	SPSettings.c_cflag |= CREAD | CLOCAL;

	// Setting the flow control
	// close the hardware control
	SPSettings.c_cflag &= ~CRTSCTS;
	
	// // close the software control
	// // close the puase function when sending and receiving data
	// // close the restart function when receive the start character
	// SPSettings.c_iflag &= ~(IXON | IXOFF | IXANY);

	// // close the process before data into the program
	// // close the local echo and sign
	// SPSettings.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG);
	// // close the process before data being sent out
	// SPSettings.c_oflag &= ~OPOST;
	
	SPSettings.c_iflag = IGNPAR;
	SPSettings.c_oflag = 0;
	SPSettings.c_lflag = 0;


    SPSettings.c_cc[VINTR] = 0;                           //* 终端处理为空
    SPSettings.c_cc[VQUIT] = 0;                           //* 退出处理为空
    SPSettings.c_cc[VERASE] = 0;                          //* 删除字符处理函数为空
    SPSettings.c_cc[VKILL] = 0;                           //* 终止字符处理函数为空
    SPSettings.c_cc[VEOF] = 0;                            //* 退出字符处理函数为空
    SPSettings.c_cc[VTIME] = 0;                           //* 超时处理函数为空
    SPSettings.c_cc[VMIN] = 1;                            //* 最小等待字符数设置为1

	if (tcsetattr (fd, TCSANOW, &SPSettings) != 0)
	{
		log ("Setting Attributes ERROR !");
		return 0;
	}
	else
	{
		log ("Setting Attribute Successfully");
		return 1;
	}
}

int senddata (int fd, std::string to_send)
{
	int write_byte = 0;
	data d;
	strcpy (d.to_send, to_send.c_str ());
	write_byte = write (fd, &d, sizeof (d));

	return write_byte;
}

int receivedata (int fd)
{
	int receive_bytes = 0;
	while (true)
	{
		char buffer[50] = " ";
		ssize_t key = read (fd, buffer, sizeof (buffer));
		if (key == -1)
		{
			if (errno == EAGAIN)
			{
				usleep (100);
				continue;
			}
			else
			{
				log ("ERROR !");
				log (errno);
				return 0;
			}
		}
		else
		{
			log (buffer);
		}
	}
}

void closeserial (int fd)
{
	close (fd);
}
